﻿using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Diagnostics;

namespace WinwiseARDrone.TouchScreenClient.Controls
{
    /// <summary>
    /// Contrôle représentant un stick analogique mono-sens
    /// </summary>
    [TemplatePart(Name = "PART_ctlStick", Type = typeof(FrameworkElement))]
    public partial class LinearStickAnalogique : Control
    {
        #region Routed Events

        public static readonly RoutedEvent ValueChangeEvent = EventManager.RegisterRoutedEvent("ValueChange", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(LinearStickAnalogique));

        #endregion

        #region Dependency Properties

        public static readonly DependencyProperty IsPressedProperty = DependencyProperty.Register("IsPressed", typeof(bool), typeof(LinearStickAnalogique), new UIPropertyMetadata(false));
        public static readonly DependencyProperty SensibilityProperty = DependencyProperty.Register("Sensibility", typeof(double), typeof(LinearStickAnalogique), new UIPropertyMetadata(1.0));
        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(LinearStickAnalogique), new UIPropertyMetadata(0.0, new PropertyChangedCallback(LinearStickAnalogique.OnValueChanged), new CoerceValueCallback(LinearStickAnalogique.CoerceValue)));

        #endregion

        #region Accesseurs Event

        /// <summary>
        /// Affecte ou retourne les handlers pour l'événement ValueChange
        /// </summary>
        public event RoutedEventHandler ValueChange
        {
            add
            {
                this.AddHandler(LinearStickAnalogique.ValueChangeEvent, value);
            }
            remove
            {
                this.RemoveHandler(LinearStickAnalogique.ValueChangeEvent, value);
            }
        }

        #endregion

        #region Accesseurs

        /// <summary>
        /// Retourne si le stick est en utilisation
        /// </summary>
        public bool IsPressed
        {
            get
            {
                return ((bool)this.GetValue(LinearStickAnalogique.IsPressedProperty));
            }
            protected set
            {
                this.SetValue(LinearStickAnalogique.IsPressedProperty, value);
            }
        }

        /// <summary>
        /// Obtient ou définit la valeur de sensibilité du stick
        /// </summary>
        public double Sensibility
        {
            get
            {
                return ((double)this.GetValue(LinearStickAnalogique.SensibilityProperty));
            }
            set
            {
                this.SetValue(LinearStickAnalogique.SensibilityProperty, value);
            }
        }

        /// <summary>
        /// Retourne la valeur de position du stick
        /// </summary>
        public double Value
        {
            get
            {
                return ((double)this.GetValue(LinearStickAnalogique.ValueProperty));
            }
            set
            {
                this.SetValue(LinearStickAnalogique.ValueProperty, value);
            }
        }

        #endregion

        #region Variables d'instances

        private FrameworkElement m_oPartStick;

        #endregion

        #region Constructeurs

        /// <summary>
        /// Constructeur statique de la classe
        /// </summary>
        static LinearStickAnalogique()
        {
            FrameworkElement.DefaultStyleKeyProperty.OverrideMetadata(typeof(LinearStickAnalogique), new FrameworkPropertyMetadata(typeof(LinearStickAnalogique)));
        }

        /// <summary>
        /// Constructeur de la classe
        /// </summary>
        public LinearStickAnalogique()
        {
            // Initialisation Tracker

            this.IsPressed = false;
            this.IsManipulationEnabled = true;

            // Création du scrutteur de manipulation

            this.ManipulationStarting += new System.EventHandler<ManipulationStartingEventArgs>(this.Affine2DManipulationStarting);
            this.ManipulationStarted += new System.EventHandler<ManipulationStartedEventArgs>(this.Affine2DManipulationStarted);
            this.ManipulationDelta += new System.EventHandler<ManipulationDeltaEventArgs>(this.Affine2DManipulationDelta);
            this.ManipulationCompleted += new System.EventHandler<ManipulationCompletedEventArgs>(this.Affine2DManipulationCompleted);
        }

        #endregion

        #region Evénements XAML

        /// <summary>
        /// Se produit sur le début de la manipulation
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void Affine2DManipulationStarted(object sender, ManipulationStartedEventArgs e)
        {
            // Positionnement stick

            this.Value = this.GetPourcentagePixel(e.ManipulationOrigin.Y - (this.ActualHeight / 2)) * this.Sensibility;

            // Déplacement en action

            this.IsPressed = true;
        }

        /// <summary>
        /// Se produit sur l'initialisation de la manipulation
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void Affine2DManipulationStarting(object sender, ManipulationStartingEventArgs e)
        {
            e.ManipulationContainer = this;
            e.Mode = ManipulationModes.TranslateY;
            e.Handled = true;
        }

        /// <summary>
        /// Se produit sur la fin de la manipulation
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void Affine2DManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
        {
            // Mise au centre

            this.Value = 0;

            // Fin tracking

            this.IsPressed = false;
        }

        /// <summary>
        /// Se produit sur le delta de la manipulation
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void Affine2DManipulationDelta(object sender, ManipulationDeltaEventArgs e)
        {
            // Mise à jour position

            if (!(e.ManipulationOrigin.Y < 0 || e.ManipulationOrigin.Y > this.ActualHeight))
                this.Value += this.GetPourcentagePixel(e.DeltaManipulation.Translation.Y) * this.Sensibility;
        }

        #endregion

        #region Fonctions publiques

        /// <summary>
        /// Appel pour application du template
        /// </summary>
        public override void OnApplyTemplate()
        {
            // Appel parent

            base.OnApplyTemplate();

            // Récupération des contrôles

            this.m_oPartStick = (FrameworkElement)this.Template.FindName("PART_ctlStick", this);
        }

        #endregion

        #region Fonctions protégées

        /// <summary>
        /// Se produit sur un changement de taille
        /// </summary>
        /// <param name="p_oSizeInfo">Informations sur le changement de taille</param>
        protected override void OnRenderSizeChanged(SizeChangedInfo p_oSizeInfo)
        {
            // On appel le parent

            base.OnRenderSizeChanged(p_oSizeInfo);

            // On rafraichis la position du stick

            this.RefreshPosition();
        }

        /// <summary>
        /// Se produit sur le changement de la propriété Value
        /// </summary>
        /// <param name="p_dOldValue">Ancienne valeur</param>
        /// <param name="p_dNewValue">Nouvelle valeur</param>
        protected virtual void OnValueChanged(double p_dOldValue, double p_dNewValue)
        {
            this.RefreshPosition();
            this.RaiseValueChange();
        }

        #endregion

        #region Fonctions privées

        /// <summary>
        /// Convertie un déplacement pixel en pourcentage
        /// </summary>
        /// <param name="p_dPixels">Nombre de pixel</param>
        /// <returns>Pourcentage de parcours</returns>
        private double GetPourcentagePixel(double p_dPixels)
        {
            return (((p_dPixels * 100) / (this.ActualHeight / 2)) / 100);
        }

        /// <summary>
        /// Lance l'événement ValueChange
        /// </summary>
        private void RaiseValueChange()
        {
            RoutedEventArgs oArgs = new RoutedEventArgs(LinearStickAnalogique.ValueChangeEvent);
            this.RaiseEvent(oArgs);
        }

        /// <summary>
        /// Rafraichis la position de l'image stick
        /// </summary>
        private void RefreshPosition()
        {
            double dPos = 0;
            double dRayonY = this.ActualHeight / 2;
            double dValue = this.Value;

            // On ramène les valeurs entre 0 et 1

            dValue = dValue / this.Sensibility;

            // Calcul de position

            dPos = (dRayonY * dValue) + dRayonY;

            // Rectification image

            Canvas.SetLeft(this.m_oPartStick, 0);
            Canvas.SetTop(this.m_oPartStick, dPos);
        }

        #endregion

        #region Fonctions statiques privées

        /// <summary>
        /// Vérifie la valeur de la propriété Value
        /// </summary>
        /// <param name="p_oItem">Object source</param>
        /// <param name="p_oBaseValue">Valeur proposée</param>
        /// <returns>Valeur correte</returns>
        private static object CoerceValue(DependencyObject p_oItem, object p_oBaseValue)
        {
            double dValue = (double)p_oBaseValue;
            LinearStickAnalogique oStick = p_oItem as LinearStickAnalogique;

            if (double.IsNaN(dValue))
                dValue = 0;
            else if (dValue > oStick.Sensibility)
                dValue = oStick.Sensibility;
            else if (dValue < (oStick.Sensibility * -1))
                dValue = oStick.Sensibility * -1;

            return (dValue);

        }

        /// <summary>
        /// Se produit sur le changement de la propriété Value
        /// </summary>
        /// <param name="p_oItem">Objet de dépendance</param>
        /// <param name="p_oArgs">Argument de l'appel</param>
        private static void OnValueChanged(DependencyObject p_oItem, DependencyPropertyChangedEventArgs p_oArgs)
        {
            (p_oItem as LinearStickAnalogique).OnValueChanged((double)p_oArgs.OldValue, (double)p_oArgs.NewValue);
        }

        #endregion
    }
}
